fix(mcp): add created_by_fk filter, field validators, and deduplicate defaults for database tools#39113
Conversation
… defaults for database tools - Add created_by_fk and changed_by_fk to DatabaseFilter columns so users can filter databases by creator (matching chart/dashboard patterns) - Add field_validator for filters and select_columns to handle JSON string parsing from MCP clients (double-serialization bug workaround) - Remove duplicate DEFAULT_DATABASE_COLUMNS from list_databases.py, import from schema_discovery.py instead - Update app.py instructions to document database created_by_fk workflow
|
Bito Automatic Review Skipped - Branch Excluded |
There was a problem hiding this comment.
Pull request overview
Improves the MCP database tools’ request ergonomics and filterability by aligning database listing schemas with existing chart/dashboard patterns, while reducing duplicated defaults.
Changes:
- Added
created_by_fkandchanged_by_fkas supported filter columns forlist_databases. - Added Pydantic
field_validators to accept JSON-string inputs forfiltersand flexible inputs forselect_columns. - Deduplicated database default columns by importing
DATABASE_DEFAULT_COLUMNSfrom the schema discovery module and updated MCP default instructions with “my databases” examples.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| superset/mcp_service/database/tool/list_databases.py | Removes duplicate default column list and uses canonical DATABASE_DEFAULT_COLUMNS. |
| superset/mcp_service/database/schemas.py | Expands filter schema and adds validators to parse JSON-string/CSV inputs for MCP clients. |
| superset/mcp_service/app.py | Documents “find my databases” workflow using created_by_fk filtering. |
| @field_validator("filters", mode="before") | ||
| @classmethod | ||
| def parse_filters(cls, v: Any) -> List[DatabaseFilter]: | ||
| """Accept both JSON string and list of objects.""" | ||
| return parse_json_or_model_list(v, DatabaseFilter, "filters") | ||
|
|
||
| @field_validator("select_columns", mode="before") | ||
| @classmethod | ||
| def parse_columns(cls, v: Any) -> List[str]: | ||
| """Accept JSON array, list, or comma-separated string.""" | ||
| return parse_json_or_list(v, "select_columns") |
There was a problem hiding this comment.
The new parsing behavior for filters and select_columns (JSON-string / CSV inputs) and the newly supported created_by_fk / changed_by_fk filter columns aren’t covered by unit tests. Please add tests (e.g., in tests/unit_tests/mcp_service/database/tool/test_database_tools.py) to verify: (1) JSON-string filters is accepted and converted into DatabaseFilter models, (2) JSON-string and comma-separated select_columns are accepted, and (3) created_by_fk filtering flows through to DatabaseDAO.list (assert the DAO is called with a ColumnOperator where col == 'created_by_fk').
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## mcp-database-tools #39113 +/- ##
======================================================
+ Coverage 64.52% 64.54% +0.01%
======================================================
Files 2540 2540
Lines 131394 131403 +9
Branches 30466 30466
======================================================
+ Hits 84779 84810 +31
+ Misses 45152 45130 -22
Partials 1463 1463
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
To cover the new parsing behavior and filtering, add these unit tests to tests/unit_tests/mcp_service/database/tool/test_database_tools.py |
| item_serializer=_serialize_database, | ||
| filter_type=DatabaseFilter, | ||
| default_columns=DEFAULT_DATABASE_COLUMNS, | ||
| default_columns=DATABASE_DEFAULT_COLUMNS, |
There was a problem hiding this comment.
Suggestion: DATABASE_DEFAULT_COLUMNS includes changed_on_humanized but not changed_on; since the serializer computes humanized time from changed_on, default responses will produce changed_on_humanized=None for row-based DAO results. Ensure changed_on is also loaded when using default columns. [logic error]
Severity Level: Major ⚠️
- ❌ list_databases default response has changed_on_humanized always null.
- ⚠️ MCP agents cannot display relative last-modified time.| default_columns=DATABASE_DEFAULT_COLUMNS, | |
| default_columns=[*DATABASE_DEFAULT_COLUMNS, "changed_on"], |
Steps of Reproduction ✅
1. Start Superset with MCP service enabled and call the `list_databases` FastMCP tool
without providing `select_columns` (default usage), which hits `list_databases()` in
`superset/mcp_service/database/tool/list_databases.py:56-63`.
2. Inside `list_databases`, a `ModelListCore` is constructed at
`superset/mcp_service/database/tool/list_databases.py:110-121` with
`default_columns=DATABASE_DEFAULT_COLUMNS` (`list_databases.py:115`), where
`DATABASE_DEFAULT_COLUMNS` is defined as `["id", "database_name", "backend",
"expose_in_sqllab", "changed_on_humanized"]` in
`superset/mcp_service/common/schema_discovery.py:61-67`.
3. Because `ListDatabasesRequest.select_columns` has a default empty list
(`superset/mcp_service/database/schemas.py:125-132`), `ModelListCore.run_tool`
(`superset/mcp_service/mcp_core.py:83-91`) treats it as falsy and sets `columns_to_load =
self.default_columns`, then calls `DatabaseDAO.list(..., columns=columns_to_load)` at
`mcp_core.py:93-102`; `BaseDAO.list` (`superset/daos/base.py:25-49,74-86`) builds a
SQLAlchemy query selecting only real column attributes (`id`, `database_name`, `backend`,
`expose_in_sqllab`) and returns Row objects that do NOT include `changed_on`.
4. Each Row is passed to `serialize_database_object` via `_serialize_database` at
`list_databases.py:103-107`, and `serialize_database_object`
(`superset/mcp_service/database/schemas.py:233-268`) calls `getattr(database,
"changed_on", None)` and `_humanize_timestamp(getattr(database, "changed_on", None))`;
since the Row lacks `changed_on`, `getattr` returns `None`, `_humanize_timestamp` returns
`None`, and both `changed_on` and `changed_on_humanized` end up as `None`. The final
response is produced by `result.model_dump(context={"select_columns": columns_to_filter})`
at `list_databases.py:145-154`, and `DatabaseInfo._filter_fields_by_context`
(`database/schemas.py:61-76`) filters fields to the requested columns, which now include
`"changed_on_humanized"` but not `"changed_on"`, so the client observes
`changed_on_humanized: null` for every database in the default `list_databases` response.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** superset/mcp_service/database/tool/list_databases.py
**Line:** 115:115
**Comment:**
*Logic Error: `DATABASE_DEFAULT_COLUMNS` includes `changed_on_humanized` but not `changed_on`; since the serializer computes humanized time from `changed_on`, default responses will produce `changed_on_humanized=None` for row-based DAO results. Ensure `changed_on` is also loaded when using default columns.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.- Fix datetime.now() without timezone in DatabaseError.create() and _humanize_timestamp() — use timezone-aware datetimes consistently - Add type hints to create_mock_database() test helper - Update mcp_core.py ModelGetSchemaCore docstring to include "database" in the model_type description
Summary
Code review improvements for #39111:
created_by_fkandchanged_by_fkto DatabaseFilter — Matches the chart/dashboard filter pattern so users can do "find my databases" queries viaget_instance_info→list_databases(filters=[{"col": "created_by_fk", ...}])field_validatorforfiltersandselect_columns— Handles JSON string parsing from MCP clients (double-serialization bug workaround), matching the pattern in DashboardFilter/ChartFilterDEFAULT_DATABASE_COLUMNS— Was defined in bothschema_discovery.pyandlist_databases.py; now imports from the canonical locationcreated_by_fkworkflow in both "find your own" and "query examples" sectionsTest plan
list_databaseswithcreated_by_fkfilter returns only databases created by the specified userfiltersandselect_columnsare parsed correctly